
#include "Camera.hpp"
#include "OpenGL.hpp"

using namespace Typhoon;

Camera::Camera():
	FieldOfView(this),
	Aspect(this),
	Near(this),
	Far(this),
	Position(this),
	Rotation(this),
	World(this),
	WorldInverse(this),
	Projection(this),
	ProjectionInverse(this)
{
	FieldOfView = 45;
	Aspect = 1;
	Near = 0.1f;
	Far = 1000.0f;
	Position = 0;
	Rotation = Quaternion::IDENTITY;


	Projection.Method += event_handler(this, &Typhoon::Camera::CalcProjectionMatrix);
	ProjectionInverse.Method += event_handler(this, &Typhoon::Camera::CalcProjectionInverseMatrix);

	World.Method += event_handler(this, &Typhoon::Camera::CalcWorldMatrix);
	WorldInverse.Method += event_handler(this, &Typhoon::Camera::CalcInverseMatrix);
	
	FieldOfView.Changed += event_handler(this, &Typhoon::Camera::ProjectionPropertyChanged);
	Aspect.Changed += event_handler(this, &Typhoon::Camera::ProjectionPropertyChanged);
	Near.Changed += event_handler(this, &Typhoon::Camera::ProjectionPropertyChanged);
	Far.Changed += event_handler(this, &Typhoon::Camera::ProjectionPropertyChanged);

	Position.Changed += event_handler(this, &Typhoon::Camera::PropertyChanged);
	Rotation.Changed += event_handler(this, &Typhoon::Camera::PropertyChanged);
}

Camera::~Camera() {}

void Camera::Begin() {
	//glPushMatrix();

	Matrix4x4& projection = *Projection.GetValue();
	
	glMatrixMode(GL_PROJECTION); 
	glLoadIdentity(); 
	glLoadMatrixf(projection.GetGlMatrix());
	
	Matrix4x4& world = *WorldInverse.GetValue();

	Matrix4x4 worldGl = world.Transpose();

	glMatrixMode (GL_MODELVIEW); 
	glLoadIdentity();
	glMultMatrixf(worldGl.GetGlMatrix());
			
	

	/*
	float zoom = Zoom;
	glScalef(zoom,zoom,1);
	glRotatef(Rotation, 0,0,1);
	*/
}

void Camera::End() {
	//glPopMatrix();
}

void Camera::ProjectionPropertyChanged(Camera* camera) {
	Projection.MakeDirty();
	ProjectionInverse.MakeDirty();
}

void Camera::PropertyChanged(Camera* camera) {
	World.MakeDirty();
	WorldInverse.MakeDirty();
}

void Camera::CalcProjectionMatrix(DirtyProperty<Camera*, Matrix4x4>::EventData& event) {
	Matrix4x4& mat = *event.Value;
	mat.InitPerspective(FieldOfView, Aspect, Near, Far);
}

void Camera::CalcProjectionInverseMatrix(DirtyProperty<Camera*, Matrix4x4>::EventData& event) {
	Matrix4x4& mat = *event.Value;
	mat = Projection.GetValue()->Invert();
}

void Camera::CalcWorldMatrix(DirtyProperty<Camera*, Matrix4x4>::EventData& event) {
	Matrix4x4& mat = *event.Value;

	Matrix4x4 position;
	Matrix4x4 rotation;
	
    Vector3 pos = Position.Get();
    position.InitTranslation(pos);

	Rotation.GetValue().ToRotationMatrix(rotation);

	mat.InitScale(1);
	mat *= rotation;
	mat *= position;
}

void Camera::CalcInverseMatrix(DirtyProperty<Camera*, Matrix4x4>::EventData& event) {
	Matrix4x4& mat = *event.Value;
	mat = World.GetValue()->Invert();
}

Vector3 Camera::ViewToWorld(Vector3 viewPosition) {
	return World.GetValue()->TransformPosition(viewPosition);
}

Vector3 Camera::WorldToView(Vector3 worldPosition) {
	return WorldInverse.GetValue()->TransformPosition(worldPosition);
}